<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  
  <title>Java Stream 理解 | SictiyLeon</title>
  
  

  

  <meta name="HandheldFriendly" content="True" />
  <meta name="apple-mobile-web-app-capable" content="yes">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
  <!-- meta -->
  
  
  <meta name='theme-color' content='#FFFFFF'>
  <meta name='msapplication-TileColor' content='#1BC3FB'>
  <meta name='msapplication-config' content='https://cdn.jsdelivr.net/gh/xaoxuu/cdn-favicon@19.9.6/browserconfig.xml'>
  

  <!-- link -->
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.css" />
  
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/node-waves@0.7.6/dist/waves.min.css">
  
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@5.10.1/css/all.min.css">
  
  
  <link rel='shortcut icon' type='image/x-icon' href='https://cdn.jsdelivr.net/gh/xaoxuu/cdn-favicon@19.9.6/favicon.ico'>
  <link rel='icon' type='image/x-icon' sizes='32x32' href='https://cdn.jsdelivr.net/gh/xaoxuu/cdn-favicon@19.9.6/favicon-32x32.png'>
  <link rel='apple-touch-icon' type='image/png' sizes='180x180' href='https://cdn.jsdelivr.net/gh/xaoxuu/cdn-favicon@19.9.6/apple-touch-icon.png'>
  <link rel='mask-icon' color='#1BC3FB' href='https://cdn.jsdelivr.net/gh/xaoxuu/cdn-favicon@19.9.6/safari-pinned-tab.svg'>
  <link rel='manifest' href='https://cdn.jsdelivr.net/gh/xaoxuu/cdn-favicon@19.9.6/site.webmanifest'>
  

  

  
    
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/xaoxuu/cdn-material-x@19.11.26/css/style.css">

  

  <script>
    function setLoadingBarProgress(num) {
      document.getElementById('loading-bar').style.width=num+"%";
    }
  </script>

  
  
<meta name="generator" content="Hexo 4.2.0"></head>

<body>
  
  
  <div class="cover-wrapper">
    <cover class='cover post half'>
      
        
  <h1 class='title'>SictiyLeon</h1>


  <div class="m_search">
    <form name="searchform" class="form u-search-form">
      <input type="text" class="input u-search-input" placeholder="" />
      <i class="icon fas fa-search fa-fw"></i>
    </form>
  </div>

<div class='menu navgation'>
  <ul class='h-list'>
    
      
        <li>
          <a class="nav home" href="/hblog/"
            
            
            id="hblog">
            <i class='fas fa-rss fa-fw'></i>&nbsp;首页
          </a>
        </li>
      
        <li>
          <a class="nav home" href="/hblog/categories/"
            
              rel="nofollow"
            
            
            id="hblogcategories">
            <i class='fas fa-code-branch fa-fw'></i>&nbsp;分类
          </a>
        </li>
      
        <li>
          <a class="nav home" href="/hblog/archives/"
            
              rel="nofollow"
            
            
            id="hblogarchives">
            <i class='fas fa-link fa-fw'></i>&nbsp;归档
          </a>
        </li>
      
        <li>
          <a class="nav home" href="/hblog/about/"
            
              rel="nofollow"
            
            
            id="hblogabout">
            <i class='fas fa-info-circle fa-fw'></i>&nbsp;关于
          </a>
        </li>
      
    
  </ul>
</div>

      
    </cover>
    <header class="l_header material">
  <div id="loading-bar-wrapper">
    <div id="loading-bar" class="material"></div>
  </div>

	<div class='wrapper'>
		<div class="nav-main container container--flex">
      <a class="logo flat-box" href='/hblog/' >
        
          SictiyLeon
        
      </a>
			<div class='menu navgation'>
				<ul class='h-list'>
          
  					
  						<li>
								<a class="nav flat-box" href="/hblog/"
                  
                  
                  id="hblog">
									<i class='fas fa-grin fa-fw'></i>&nbsp;首页
								</a>
							</li>
      			
  						<li>
								<a class="nav flat-box" href="/hblog/categories/"
                  
                    rel="nofollow"
                  
                  
                  id="hblogcategories">
									<i class='fas fa-folder-open fa-fw'></i>&nbsp;分类
								</a>
							</li>
      			
  						<li>
								<a class="nav flat-box" href="/hblog/archives/"
                  
                    rel="nofollow"
                  
                  
                  id="hblogarchives">
									<i class='fas fa-archive fa-fw'></i>&nbsp;归档
								</a>
							</li>
      			
      		
				</ul>
			</div>

			
				<div class="m_search">
					<form name="searchform" class="form u-search-form">
						<input type="text" class="input u-search-input" placeholder="Search" />
						<i class="icon fas fa-search fa-fw"></i>
					</form>
				</div>
			
			<ul class='switcher h-list'>
				
					<li class='s-search'><a class="fas fa-search fa-fw" href='javascript:void(0)'></a></li>
				
				<li class='s-menu'><a class="fas fa-bars fa-fw" href='javascript:void(0)'></a></li>
			</ul>
		</div>

		<div class='nav-sub container container--flex'>
			<a class="logo flat-box"></a>
			<ul class='switcher h-list'>
				<li class='s-comment'><a class="flat-btn fas fa-comments fa-fw" href='javascript:void(0)'></a></li>
        
          <li class='s-toc'><a class="flat-btn fas fa-list fa-fw" href='javascript:void(0)'></a></li>
        
			</ul>
		</div>
	</div>
</header>
	<aside class="menu-phone">
    <header>
		<nav class="menu navgation">
      <ul>
        
          
            <li>
							<a class="nav flat-box" href="/hblog/"
                
                
                id="hblog">
								<i class='fas fa-clock fa-fw'></i>&nbsp;首页
							</a>
            </li>
          
            <li>
							<a class="nav flat-box" href="/hblog/categories/"
                
                  rel="nofollow"
                
                
                id="hblogcategories">
								<i class='fas fa-folder-open fa-fw'></i>&nbsp;分类
							</a>
            </li>
          
            <li>
							<a class="nav flat-box" href="/hblog/archives/"
                
                  rel="nofollow"
                
                
                id="hblogarchives">
								<i class='fas fa-archive fa-fw'></i>&nbsp;归档
							</a>
            </li>
          
            <li>
							<a class="nav flat-box" href="/hblog/about/"
                
                  rel="nofollow"
                
                
                id="hblogabout">
								<i class='fas fa-info-circle fa-fw'></i>&nbsp;关于
							</a>
            </li>
          
       
      </ul>
		</nav>
    </header>
	</aside>
<script>setLoadingBarProgress(40);</script>

  </div>


  <div class="l_body">
    <div class='body-wrapper'>
      <div class='l_main'>
  

  <article id="post" class="post white-box article-type-post" itemscope itemprop="blogPost">
    


  <section class='meta'>
    
    
    <div class="meta" id="header-meta">
      
        
  
    <h1 class="title">
      <a href="/hblog/2020/01/14/stream/">
        Java Stream 理解
      </a>
    </h1>
  


      
      <div class='new-meta-box'>
        
          
        
          
            
  <div class='new-meta-item author'>
    
      <a href="https://www.sictiy.cn/hblog" rel="nofollow">
        
          <i class="fas fa-user" aria-hidden="true"></i>
        
        <p>sictiy xu</p>
      </a>
    
  </div>


          
        
          
            <div class="new-meta-item date">
  <a class='notlink'>
    <i class="fas fa-calendar-alt" aria-hidden="true"></i>
    <p>2020-01-14</p>
  </a>
</div>

          
        
          
            
  
  <div class='new-meta-item category'>
    <a href='/hblog/categories/java/' rel="nofollow">
      <i class="fas fa-folder-open" aria-hidden="true"></i>
      <p>java</p>
    </a>
  </div>


          
        
          
            
  
    <div class="new-meta-item browse busuanzi">
      <a class='notlink'>
        <i class="fas fa-eye" aria-hidden="true"></i>
        <p>
          <span id="busuanzi_value_page_pv">
            <i class="fas fa-spinner fa-spin fa-fw" aria-hidden="true"></i>
          </span>
        </p>
      </a>
    </div>
  


          
        
          
            
  
    <div class="new-meta-item wordcount">
      <a class='notlink'>
        <i class="fas fa-keyboard" aria-hidden="true"></i>
        <p>字数统计:</p>
        <p>3.5k字</p>
      </a>
    </div>
    <div class="new-meta-item readtime">
      <a class='notlink'>
        <i class="fas fa-hourglass-half" aria-hidden="true"></i>
        <p>阅读时长≈</p>
        <p>13分</p>
      </a>
    </div>
  

          
        
          
            

          
        
      </div>
      
        <hr>
      
    </div>
  </section>


    <section class="article typo">
      <div class="article-entry" itemprop="articleBody">
        <h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><h3 id="什么叫流"><a href="#什么叫流" class="headerlink" title="什么叫流"></a>什么叫流</h3><p>Java 8 中的 Stream 是对集合（Collection）对象功能的增强，它专注于对集合对象进行各种非常便利、高效的聚合操作，或者大批量数据操作 。Stream API 借助于同样新出现的 Lambda 表达式，极大的提高编程效率和程序可读性.</p>
<p><img src="https://s2.ax1x.com/2020/01/15/lLT3Ed.png" alt="stream-pipeline"></p>
<p>与直接对集合类进行连续的一系列操作不同，使用流并非对集合类的所有元素进行一个操作之后得到中间集合再进行下一步的操作，而是针对流中的每一个元素按流的方向依次执行所有操作，不存在中间集合，所有的这些流式操作都在终端操作(如图中的ForEach)执行时才开始执行。</p>
<h3 id="流的特点"><a href="#流的特点" class="headerlink" title="流的特点"></a>流的特点</h3><ul>
<li>流管道由数据源、中间操作、终端操作组成。终端操作可以没有也可以有多个，数据源和终端操作只有一个。</li>
<li>流不存储数据、流可能是无界的、流是可以被消耗的。流通过数据源不断生成单个元素，当整个流执行完终端操作之后流被标记成已消耗，无法再次执行终段操作。</li>
<li>流的操作基本都是函数式接口的实例，中间操作的函数式操作是延迟执行的。</li>
<li>流分为顺序执行和并行执行两种方式。并行执行依赖数据源Spliterator体验的并行遍历机制。</li>
</ul>
<h2 id="预备"><a href="#预备" class="headerlink" title="预备"></a>预备</h2><h3 id="Spliterator"><a href="#Spliterator" class="headerlink" title="Spliterator"></a>Spliterator</h3><p>Spliterator为Iterator的并行版本，提供对集合数据的并行遍历能力。</p>
<p><img src="https://s2.ax1x.com/2020/01/15/lLqMRI.png" alt="stream-spliterator"></p>
<p>这里主要依赖三个方法: tryAdvance, forEachRemaining, trySplit。</p>
<ul>
<li>tryAdvance 生成一个元素并对该元素执行传入的Consumer操作，返回的boolean值代表是否还可以继续生成下一个元素</li>
<li>forEachRemaining 顾名思义该方法为对所有剩余生成的元素执行传入到的Consumer操作，该方法的默认实现为循环调用tryAdvance方法，直到其返回值为false。</li>
<li>trySplit 该方法用于实现并行遍历。调用该方法后该Spliterator会拆分另一个Spliterator并返回，两个Spliterator可在两个线程中进行并行遍历。</li>
</ul>
<h3 id="Functor"><a href="#Functor" class="headerlink" title="Functor"></a>Functor</h3><p>Functor函子本是范畴论中的一个概念，指范畴间的一类映射。在函数式编程里，主要指对普通对象的封装，相对普通函数对简单对象进行操作映射，函子函数对高阶对象进行操作映射。</p>
<p>可以通过一段示例代码进行理解：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 函子</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> sictiy.xu</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@version</span> 2020/01/06 10:03</span></span><br><span class="line"><span class="comment"> **/</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">Functor</span>&lt;<span class="title">T</span>, <span class="title">F</span> <span class="keyword">extends</span> <span class="title">Functor</span>&lt;?, ?&gt;&gt;</span></span><br><span class="line"><span class="class"></span>&#123;</span><br><span class="line">    &lt;R&gt; <span class="function">F <span class="title">map</span><span class="params">(Function&lt;T, R&gt; f)</span></span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 通用函子</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> sictiy.xu</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@version</span> 2020/01/06 10:05</span></span><br><span class="line"><span class="comment"> **/</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">CommonFunctor</span>&lt;<span class="title">T</span>&gt; <span class="keyword">implements</span> <span class="title">Functor</span>&lt;<span class="title">T</span>, <span class="title">CommonFunctor</span>&lt;?&gt;&gt;</span></span><br><span class="line"><span class="class"></span>&#123;</span><br><span class="line">    <span class="keyword">private</span> T value;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">CommonFunctor</span><span class="params">(T value)</span></span></span><br><span class="line"><span class="function">    </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.value = value;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> &lt;R&gt; <span class="function">CommonFunctor&lt;R&gt; <span class="title">map</span><span class="params">(Function&lt;T, R&gt; f)</span></span></span><br><span class="line"><span class="function">    </span>&#123;</span><br><span class="line">        <span class="keyword">final</span> R result = f.apply(value);</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> CommonFunctor&lt;&gt;(result);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> T <span class="title">getValue</span><span class="params">()</span></span></span><br><span class="line"><span class="function">    </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> value;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span></span></span><br><span class="line"><span class="function">    </span>&#123;</span><br><span class="line">        <span class="keyword">var</span> functor = <span class="keyword">new</span> CommonFunctor&lt;&gt;(<span class="number">10</span>);</span><br><span class="line">        LogUtil.info(<span class="string">"&#123;&#125;"</span>, functor.map(a -&gt; a + <span class="number">1</span>).map(a -&gt; a * <span class="number">10</span>).getValue());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>函子接口Functor定义函子函数map，所有的函子都需要实现该接口。在实现类中，函子对象封装了普通对象T，而函子函数接收一个Function操作，该Function操作对普通对象T进行处理，结果为普通对象R。函子函数解析函子中封装的普通对象执行Function操作后将结果封装成新的函子对象。由于函子对象的map方法返回的是一个新的函子对象，所以可以连续的调用链式调用map方法直到调用getValue方法解析最后一个函子对象所封装的普通对象。该Functor虽然形式上与Stream一样，可以链式调用对象的方法，但是每一步调用依然是即时操作的，那如果对传入的操作并不马上操作，而是将传入的函数式接口通过对象属性或者保存在抽象方法的实现中，然后在最后需要获得结果的时候再统一处理所有的操作，是不是就是Stream实现的基本思想呢？</p>
<h3 id="Stream基本用法"><a href="#Stream基本用法" class="headerlink" title="Stream基本用法"></a>Stream基本用法</h3><p><img src="https://s2.ax1x.com/2020/01/15/lO6LO1.png" alt="stream-op"></p>
<p>Stream中的操作可以分为两大类：中间操作与结束操作，中间操作只是对操作进行了记录，只有结束操作才会触发实际的计算（即惰性求值），这也是Stream在迭代大集合时高效的原因之一。中间操作又可以分为无状态（Stateless）操作与有状态（Stateful）操作，前者是指元素的处理不受之前元素的影响；后者是指该操作只有拿到所有元素之后才能继续下去。结束操作又可以分为短路与非短路操作，这个应该很好理解，前者是指遇到某些符合条件的元素就可以得到最终结果；而后者是指必须处理所有元素才能得到最终结果。</p>
<h2 id="流程原理"><a href="#流程原理" class="headerlink" title="流程原理"></a>流程原理</h2><h3 id="一个小栗子"><a href="#一个小栗子" class="headerlink" title="一个小栗子"></a>一个小栗子</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">testStream</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    String[] words = &#123;<span class="string">"hello"</span>, <span class="string">"my"</span>, <span class="string">"world"</span>&#125;;</span><br><span class="line">    <span class="keyword">var</span> strStream = Arrays.stream(words);</span><br><span class="line">    <span class="keyword">var</span> tempStream = strStream.filter(word -&gt; word.length() &gt; <span class="number">3</span>)</span><br><span class="line">            .map(word -&gt; word.split(<span class="string">""</span>))</span><br><span class="line">            .flatMap(Arrays::stream)</span><br><span class="line">            .map(String::toUpperCase);</span><br><span class="line">    <span class="keyword">var</span> result = tempStream.reduce((a, b) -&gt; a + <span class="string">","</span> + b);</span><br><span class="line">    LogUtil.info(result.orElse(<span class="string">"null"</span>));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>结果为：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[2020-01-15 10:49:56:721] [INFO ] com.sictiy.jserver.Test.testStream(Test.java:44) H,E,L,L,O,W,O,R,L,D</span><br></pre></td></tr></table></figure>

<p>先看一下通过Arrays.stream方法创建的Stream是什么样的：</p>
<p><img src="https://s2.ax1x.com/2020/01/15/lLxrO1.png" alt="strStram"></p>
<p>首先返回的strSteam是个***PipeLine$Head的实例，显然这是一个Stream的实现类，该类有一个属性sourceStage指向自己，有一个属性sourceSpliterator是根据传入的数组生成的一个ArraySpliterator实例。</p>
<p>按Functor的思想，函子函数接收一个函数式接口应该返回另一个函子对象，那stream是怎么样的呢？再看一下filter函数里面是怎么实现的：</p>
<p><img src="https://s2.ax1x.com/2020/01/15/lOS9vd.png" alt="filter"></p>
<p>filter方法返回了一个叫做StatelessOp的Stream接口的实现，但在方法体内除了对传入的predicate进行空指针判断以外并没有进行其他的处理。返回的StatelessOp类重写了opWarpSink方法，而onWarpSink方法返回一个Sink接口，该Sink接口实现了accept方法，接收一个对象，如果满足predicate，则执行downstream的accept方法，这个sink可以理解为前一个操作调用该sink的accept接口，如果入参满足predicate，那继续调用下一个操作。我们已经知道了传入的predicate存放到的位置，但依然不知道这个sink什么时候使用，怎么使用。</p>
<p>知道了增加一个中间操作做了什么以后，再看一下调用了一堆中间操作后返回到的tempStream是什么样的：</p>
<p><img src="https://s2.ax1x.com/2020/01/15/lO9lNV.png" alt="tempStream"></p>
<p>该Stream的结构很清晰，就是一个双向链表结构，所有的sourceStage指向最先生成的那个头，previousStage从最后生成的Stream一直往前指向头，而nextStage则从头往后一直指向最后一个Stream，结构是这样的：</p>
<p><img src="https://s2.ax1x.com/2020/01/15/lLT9jU.png" alt="stage"></p>
<p>到目前为止通过这个栗子把从创建的Head，到调用若干次中间操作后返回的StatelessOp的流程、以及最后返回的实例的整体结构都了解了一遍。</p>
<h3 id="Stream族谱"><a href="#Stream族谱" class="headerlink" title="Stream族谱"></a>Stream族谱</h3><p><img src="https://s2.ax1x.com/2020/01/15/lLTujO.png" alt="stream-class"></p>
<ul>
<li>BaseStream类是所有Stream的基本接口，定义了是否并行，数据源，关闭回调等基本方法，Stream流继承了AutoCloseable接口不需要手动关闭。</li>
<li>BaseStream有四个分支，其中Stream处理的元素为引用类型而另外三个分别处理int，lang，double三种基本类型的数据。</li>
<li>AbstractPipeLine 定义了Stream双向链表的管道结构。</li>
<li>PipeLineHelper 为管道辅助类，主要定义了一些终端操作中与计算相关的一些方法，比如WarpSink、copyInto等。</li>
<li>***PipeLine里定义了Head StatelessOp StatefulOp 三种具体的实现类，初始的Stream为Head，中间操作后生成的根据操作的不同分为Stateless和StatefulOp</li>
</ul>
<h3 id="终端操作流程"><a href="#终端操作流程" class="headerlink" title="终端操作流程"></a>终端操作流程</h3><p>回到之前的小栗子，看双向链表结构的Pipeline是怎么通过warpSink组合不同的操作再讲操作运用到产生元素的Spliterator的。</p>
<p><img src="https://s2.ax1x.com/2020/01/15/lOAkhq.png" alt="stream-reduce"></p>
<p>终端操作reduce调用了***PipeLine的evaluate方法，传入了一个ReduceOps的实例。ReduceOps代表了reduce这种终端操作，是TerminalOp接口的一个实现。</p>
<p><img src="https://s2.ax1x.com/2020/01/15/lOV36K.png" alt="stream-evaluatea"></p>
<p>在evaluate方法里根据条件是否并行分别调用了 TerminalOp的并行计算与顺序计算方法，传入参数为类型为pipelinehelper的this本身，和产生元素的Spliterator。</p>
<p><img src="https://s2.ax1x.com/2020/01/15/lOZ6v6.png" alt="stream-seqEvaluate" title="计算过程"><br><img src="https://s2.ax1x.com/2020/01/15/lOeQsK.png" alt="stream-makeSink" title="makeSink"><br><img src="https://s2.ax1x.com/2020/01/15/lOmmTg.png" alt="stream-warpSink" title="warpSink"><br><img src="https://s2.ax1x.com/2020/01/15/lOm360.png" alt="stream-copyInto" title="copyInto"></p>
<p>以顺序执行为例，计算过程分为makeSink warpSink 和 copyInto 三个过程。</p>
<ul>
<li>makeSink由终端操作实例提供，生成第一个sink操作，代表整个操作流中的最后一个sink。</li>
<li>warpSink 由 PipelineHelper提供，根据管道结构从最后一个中间操作开始不断往前调用***Pipeline的opWarpSink方法，将所有中间操作和终端操作进行组合，返回一个组合了所有操作的最终Sink。</li>
<li>copyInto 同样由PipelineHelper提供，用于将生成的最终Sink应用于传入的Spliterator，使每一个元素依次执行这个Sink方法。</li>
</ul>
<h4 id="Sink"><a href="#Sink" class="headerlink" title="Sink"></a>Sink</h4><p>Sink是组合流管道流式操作的媒介，继承至Comsummer接口，可以对传入的元素进行一定的处理，Sink接口主要有以下方法：</p>
<p><img src="https://s2.ax1x.com/2020/01/15/lO01IJ.png" alt="stream-sink"></p>
<p>Sink有两种状态，初始状态和激活状态begin后进入激活状态，end后重新进入初始状态，accept方法只有在激活状态才能使用。Sink通过终端操作往前封装，后一个sink传入前一个Sink，保存在downStream属性中，每一个PipeLine 通过实现onWarpSink方法实现当前操作与下一步操作的封装，如下图，filter方法返回的Pipeline实现的opWarpSink方法返回当前操作的Sink，该Sink接收一个对象，如果对象满足predicate则将对象传递给downStream继续处理下一个操作：</p>
<p><img src="https://s2.ax1x.com/2020/01/15/lOS9vd.png" alt="filter"></p>
<h4 id="Stream的工作流程总览"><a href="#Stream的工作流程总览" class="headerlink" title="Stream的工作流程总览"></a>Stream的工作流程总览</h4><p><img src="https://s2.ax1x.com/2020/01/15/lLogne.png" alt="stream-all"></p>
<h3 id="另一个栗子"><a href="#另一个栗子" class="headerlink" title="另一个栗子"></a>另一个栗子</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">testStream</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    String[] words = &#123;<span class="string">"hello"</span>, <span class="string">"my"</span>, <span class="string">"world"</span>&#125;;</span><br><span class="line">    <span class="keyword">var</span> strStream = Arrays.stream(words);</span><br><span class="line">    <span class="keyword">var</span> tempStream = strStream.filter(word -&gt; word.length() &gt; <span class="number">3</span>)</span><br><span class="line">            .map(word -&gt; word.split(<span class="string">""</span>))</span><br><span class="line">            .flatMap(Arrays::stream)</span><br><span class="line">            .distinct()</span><br><span class="line">            .map(String::toUpperCase)</span><br><span class="line">            .sorted();</span><br><span class="line">    <span class="keyword">var</span> result = tempStream.reduce((a, b) -&gt; a + <span class="string">","</span> + b);</span><br><span class="line">    LogUtil.info(result.orElse(<span class="string">"null"</span>));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>输出结果：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[2020-01-15 14:55:01:444] [INFO ] com.sictiy.jserver.Test.testStream(Test.java:46) D,E,H,L,O,R,W</span><br></pre></td></tr></table></figure>

<h4 id="有状态操作"><a href="#有状态操作" class="headerlink" title="有状态操作"></a>有状态操作</h4><p>当栗子里面加入了sorted和distinct两个中间操作后，原来的逻辑好像有了点问题。按原来的流程，所有操作组合成一个流式的Sink后，Spliterator生成序列的元素，依次通过Sink处理，各元素之间相互不影响。但是当操作中出现有状态操作时，比如sorted需要依赖各个元素之间的大小关系，显然无法生成一个元素处理一个元素，那这种有状态操作又是如何处理的呢？通过观察第二个栗子的reduce执行步骤发现，整体流程依然没有变化，变化的只是双向链表的单个操作节点由StateleOp替换为了StatefulOp的实现类，比如sorted方法返回的是实现类SortedOps。我们已经知道不同操作节点之间的操作斜街是通过opWarpSink实现的，那有状态操作与无状态操作最大的不同肯定就在于opWarpSink了，来看一下该方法返回的是什么样的Sink：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">    * &#123;<span class="doctag">@link</span> Sink&#125; for implementing sort on reference streams.</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">RefSortingSink</span>&lt;<span class="title">T</span>&gt; <span class="keyword">extends</span> <span class="title">AbstractRefSortingSink</span>&lt;<span class="title">T</span>&gt; </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> ArrayList&lt;T&gt; list;</span><br><span class="line"></span><br><span class="line">    RefSortingSink(Sink&lt;? <span class="keyword">super</span> T&gt; sink, Comparator&lt;? <span class="keyword">super</span> T&gt; comparator) &#123;</span><br><span class="line">        <span class="keyword">super</span>(sink, comparator);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">begin</span><span class="params">(<span class="keyword">long</span> size)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (size &gt;= Nodes.MAX_ARRAY_SIZE)</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> IllegalArgumentException(Nodes.BAD_SIZE);</span><br><span class="line">        list = (size &gt;= <span class="number">0</span>) ? <span class="keyword">new</span> ArrayList&lt;&gt;((<span class="keyword">int</span>) size) : <span class="keyword">new</span> ArrayList&lt;&gt;();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">end</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        list.sort(comparator);</span><br><span class="line">        downstream.begin(list.size());</span><br><span class="line">        <span class="keyword">if</span> (!cancellationRequestedCalled) &#123;</span><br><span class="line">            list.forEach(downstream::accept);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">for</span> (T t : list) &#123;</span><br><span class="line">                <span class="keyword">if</span> (downstream.cancellationRequested()) <span class="keyword">break</span>;</span><br><span class="line">                downstream.accept(t);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        downstream.end();</span><br><span class="line">        list = <span class="keyword">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">accept</span><span class="params">(T t)</span> </span>&#123;</span><br><span class="line">        list.add(t);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>该Sink的accept方法接收一个元素后并没有将这个元素继续往下传递，而是存放在list当中，当所有元素都生成完之后，copyInto方法中调用end方法，当end传递到sorted操作这一层的时候，先对list进行排序，然后依次对其中的元素调用downstream的accept方法向下传递，所有元素都处理完之后，再传递end方法的调用。相当于当元素流在操作管道中传递的时候，在这一步截断了，对所有元素排序后，继续依次往后传递。既然排序操作是这样的，那去重操作呢，再看一下：</p>
<p>未排序的stream：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">return</span> <span class="keyword">new</span> Sink.ChainedReference&lt;T, T&gt;(sink) &#123;</span><br><span class="line">    Set&lt;T&gt; seen;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">begin</span><span class="params">(<span class="keyword">long</span> size)</span> </span>&#123;</span><br><span class="line">        seen = <span class="keyword">new</span> HashSet&lt;&gt;();</span><br><span class="line">        downstream.begin(-<span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">end</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        seen = <span class="keyword">null</span>;</span><br><span class="line">        downstream.end();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">accept</span><span class="params">(T t)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (!seen.contains(t)) &#123;</span><br><span class="line">            seen.add(t);</span><br><span class="line">            downstream.accept(t);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>已排序的stream：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">return</span> <span class="keyword">new</span> Sink.ChainedReference&lt;T, T&gt;(sink) &#123;</span><br><span class="line">    <span class="keyword">boolean</span> seenNull;</span><br><span class="line">    T lastSeen;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">begin</span><span class="params">(<span class="keyword">long</span> size)</span> </span>&#123;</span><br><span class="line">        seenNull = <span class="keyword">false</span>;</span><br><span class="line">        lastSeen = <span class="keyword">null</span>;</span><br><span class="line">        downstream.begin(-<span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">end</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        seenNull = <span class="keyword">false</span>;</span><br><span class="line">        lastSeen = <span class="keyword">null</span>;</span><br><span class="line">        downstream.end();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">accept</span><span class="params">(T t)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (t == <span class="keyword">null</span>) &#123;</span><br><span class="line">            <span class="keyword">if</span> (!seenNull) &#123;</span><br><span class="line">                seenNull = <span class="keyword">true</span>;</span><br><span class="line">                downstream.accept(lastSeen = <span class="keyword">null</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (lastSeen == <span class="keyword">null</span> || !t.equals(lastSeen)) &#123;</span><br><span class="line">            downstream.accept(lastSeen = t);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>显然该Sink就可以达到去重的目的，当Stream是已排序的时，重复元素只可能连续出现，只需要与上一次传入的元素对比，如果相同则已处理过重复元素直接丢弃；当Stream时未排序的时，将已处理的元素记录在set中，当下次接收到相同元素时丢弃。所谓有状态，即时在当前操作处理一个元素之后需要记录某些信息，决定后面的元素的操作，而这种机制就是依靠Sink来实现的，不同的操作实现不同的opWarpSink，返回不同的Sink，Sink中可以记录某些信息来实现有状态。</p>

      </div>
      
      
        <br>
        


  <section class='meta' id="footer-meta">
    <div class='new-meta-box'>
      
        
          <div class="new-meta-item date" itemprop="dateUpdated" datetime="2020-01-16T11:28:47+08:00">
  <a class='notlink'>
    <i class="fas fa-clock" aria-hidden="true"></i>
    <p>updated at Jan 16, 2020</p>
  </a>
</div>

        
      
        
          

        
      
        
          
  <div class="new-meta-item share -mob-share-list">
  <div class="-mob-share-list share-body">
    
      
        <a class="-mob-share-qq" title="QQ好友" rel="external nofollow noopener noreferrer"
          
          href="http://connect.qq.com/widget/shareqq/index.html?url=https://www.sictiy.cn/hblog/2020/01/14/stream/&title=Java Stream 理解 | SictiyLeon&summary="
          
          >
          
            <img src="https://cdn.jsdelivr.net/gh/xaoxuu/assets@19.1.9/logo/128/qq.png">
          
        </a>
      
    
      
        <a class="-mob-share-qzone" title="QQ空间" rel="external nofollow noopener noreferrer"
          
          href="https://sns.qzone.qq.com/cgi-bin/qzshare/cgi_qzshare_onekey?url=https://www.sictiy.cn/hblog/2020/01/14/stream/&title=Java Stream 理解 | SictiyLeon&summary="
          
          >
          
            <img src="https://cdn.jsdelivr.net/gh/xaoxuu/assets@19.1.9/logo/128/qzone.png">
          
        </a>
      
    
      
        <a class="-mob-share-weibo" title="微博" rel="external nofollow noopener noreferrer"
          
          href="http://service.weibo.com/share/share.php?url=https://www.sictiy.cn/hblog/2020/01/14/stream/&title=Java Stream 理解 | SictiyLeon&summary="
          
          >
          
            <img src="https://cdn.jsdelivr.net/gh/xaoxuu/assets@19.1.9/logo/128/weibo.png">
          
        </a>
      
    
  </div>
</div>



        
      
    </div>
  </section>


      
      
          <div class="prev-next">
              
                  <section class="prev">
                      <span class="art-item-left">
                          <h6><i class="fas fa-chevron-left" aria-hidden="true"></i>&nbsp;Previous</h6>
                          <h4>
                              <a href="/hblog/2020/01/15/charMatch/" rel="prev" title="字符串匹配">
                                
                                    字符串匹配
                                
                              </a>
                          </h4>
                          
                      </span>
                  </section>
              
              
                  <section class="next">
                      <span class="art-item-right" aria-hidden="true">
                          <h6>Next&nbsp;<i class="fas fa-chevron-right" aria-hidden="true"></i></h6>
                          <h4>
                              <a href="/hblog/2019/09/20/dig/" rel="prev" title="炮弹挖坑问题">
                                  
                                      炮弹挖坑问题
                                  
                              </a>
                          </h4>
                          
                      </span>
                  </section>
              
          </div>
      
    </section>
  </article>



  <!-- 显示推荐文章和评论 -->



  <article class="post white-box comments">
    <section class="article typo">
      <h4><i class="fas fa-comments fa-fw" aria-hidden="true"></i>&nbsp;Comments</h4>
      
      
      
      
        <section id="comments">
          <div id="valine_container" class="valine_thread">
            <i class="fas fa-spinner fa-spin fa-fw"></i>
          </div>
        </section>
      
    </section>
  </article>






<!-- 根据页面mathjax变量决定是否加载MathJax数学公式js -->



  <script>
    window.subData = {
      title: 'Java Stream 理解',
      tools: true
    }
  </script>


</div>
<aside class='l_side'>
  
    
    
      
      
        
          
          
            
              <section class='widget author'>
  <div class='content material'>
    
      <div class='avatar'>
        <img class='avatar' src='https://s2.ax1x.com/2020/01/17/lxqAEV.jpg'/>
      </div>
    
    
    
      <div class="social-wrapper">
        
          
            <a href="/hblog/xlm104600@gmail.com"
              class="social fas fa-envelope flat-btn"
              target="_blank"
              rel="external nofollow noopener noreferrer">
            </a>
          
        
          
            <a href="https://github.com/Sictiy"
              class="social fab fa-github flat-btn"
              target="_blank"
              rel="external nofollow noopener noreferrer">
            </a>
          
        
          
            <a href="https://music.163.com/#/user/home?id=98830575"
              class="social fas fa-headphones-alt flat-btn"
              target="_blank"
              rel="external nofollow noopener noreferrer">
            </a>
          
        
      </div>
    
  </div>
</section>

            
          
        
          
          
        
          
          
        
          
          
        
          
          
        
          
          
        
          
          
        
      
        
          
          
        
          
          
        
          
          
            
              <section class='widget grid'>
  
<header class='material'>
  <div><i class="fas fa-map-signs fa-fw" aria-hidden="true"></i>&nbsp;&nbsp;站内导航</div>
  
</header>

  <div class='content material'>
    <ul class="grid navgation">
      
        <li><a class="flat-box" title="/hblog/" href="/hblog/"
          
          
          id="hblog">
          
            <i class="fas fa-clock fa-fw" aria-hidden="true"></i>
          
          近期文章
        </a></li>
      
        <li><a class="flat-box" title="/hblog/archives/" href="/hblog/archives/"
          
            rel="nofollow"
          
          
          id="hblogarchives">
          
            <i class="fas fa-archive fa-fw" aria-hidden="true"></i>
          
          文章归档
        </a></li>
      
        <li><a class="flat-box" title="/hblog/about/" href="/hblog/about/"
          
            rel="nofollow"
          
          
          id="hblogabout">
          
            <i class="fas fa-info-circle fa-fw" aria-hidden="true"></i>
          
          关于小站
        </a></li>
      
    </ul>
  </div>
</section>

            
          
        
          
          
        
          
          
        
          
          
        
          
          
        
      
        
          
          
        
          
          
            
              
  <section class='widget toc-wrapper'>
    
<header class='material'>
  <div><i class="fas fa-list fa-fw" aria-hidden="true"></i>&nbsp;&nbsp;本文目录</div>
  
    <!-- <div class='wrapper'><a class="s-toc rightBtn" rel="external nofollow noopener noreferrer" href="javascript:void(0)"><i class="fas fa-thumbtack fa-fw"></i></a></div> -->
  
</header>

    <div class='content material'>
      <ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#概述"><span class="toc-text">概述</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#什么叫流"><span class="toc-text">什么叫流</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#流的特点"><span class="toc-text">流的特点</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#预备"><span class="toc-text">预备</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#Spliterator"><span class="toc-text">Spliterator</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Functor"><span class="toc-text">Functor</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Stream基本用法"><span class="toc-text">Stream基本用法</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#流程原理"><span class="toc-text">流程原理</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#一个小栗子"><span class="toc-text">一个小栗子</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Stream族谱"><span class="toc-text">Stream族谱</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#终端操作流程"><span class="toc-text">终端操作流程</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#Sink"><span class="toc-text">Sink</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#Stream的工作流程总览"><span class="toc-text">Stream的工作流程总览</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#另一个栗子"><span class="toc-text">另一个栗子</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#有状态操作"><span class="toc-text">有状态操作</span></a></li></ol></li></ol></li></ol>
    </div>
  </section>


            
          
        
          
          
        
          
          
        
          
          
        
          
          
        
          
          
        
      
        
          
          
        
          
          
        
          
          
        
          
          
            
              
  <section class='widget category'>
    
<header class='material'>
  <div><i class="fas fa-folder-open fa-fw" aria-hidden="true"></i>&nbsp;&nbsp;文章分类</div>
  
    <a class="rightBtn"
    
      rel="nofollow"
    
    
    href="/hblog/categories/"
    title="categories/">
    <i class="fas fa-expand-arrows-alt fa-fw"></i></a>
  
</header>

    <div class='content material'>
      <ul class="entry">
        
          <li><a class="flat-box" title="/hblog/categories/algorithm/" href="/hblog/categories/algorithm/"><div class='name'>algorithm</div><div class='badge'>(2)</div></a></li>
        
          <li><a class="flat-box" title="/hblog/categories/c/" href="/hblog/categories/c/"><div class='name'>c++</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box" title="/hblog/categories/go/" href="/hblog/categories/go/"><div class='name'>go</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box" title="/hblog/categories/java/" href="/hblog/categories/java/"><div class='name'>java</div><div class='badge'>(5)</div></a></li>
        
          <li><a class="flat-box" title="/hblog/categories/server/" href="/hblog/categories/server/"><div class='name'>server</div><div class='badge'>(2)</div></a></li>
        
          <li><a class="flat-box" title="/hblog/categories/%E6%B5%8B%E8%AF%95/" href="/hblog/categories/%E6%B5%8B%E8%AF%95/"><div class='name'>测试</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box" title="/hblog/categories/%E9%85%8D%E7%BD%AE/" href="/hblog/categories/%E9%85%8D%E7%BD%AE/"><div class='name'>配置</div><div class='badge'>(1)</div></a></li>
        
      </ul>
    </div>
  </section>


            
          
        
          
          
        
          
          
        
          
          
        
      
    

  
</aside>

<footer id="footer" class="clearfix">
  
  
    <div class="social-wrapper">
      
        
          <a href="/hblog/xlm104600@gmail.com"
            class="social fas fa-envelope flat-btn"
            target="_blank"
            rel="external nofollow noopener noreferrer">
          </a>
        
      
        
          <a href="https://github.com/Sictiy"
            class="social fab fa-github flat-btn"
            target="_blank"
            rel="external nofollow noopener noreferrer">
          </a>
        
      
        
          <a href="https://music.163.com/#/user/home?id=98830575"
            class="social fas fa-headphones-alt flat-btn"
            target="_blank"
            rel="external nofollow noopener noreferrer">
          </a>
        
      
    </div>
  
  <br>
  <div><p>Blog content follows the <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en" target="_blank" rel="noopener">Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0) License</a></p>
</div>
  <div>
    Use
    <a href="https://xaoxuu.com/wiki/material-x/" target="_blank" class="codename">Material X</a>
    as theme
    
      , 
      total visits
      <span id="busuanzi_value_site_pv"><i class="fas fa-spinner fa-spin fa-fw" aria-hidden="true"></i></span>
      times
    
    . 
  </div>
</footer>
<script>setLoadingBarProgress(80);</script>


      <script>setLoadingBarProgress(60);</script>
    </div>
    <a class="s-top fas fa-arrow-up fa-fw" href='javascript:void(0)'></a>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/jquery@3.3.1/dist/jquery.min.js"></script>

  <script>
    var GOOGLE_CUSTOM_SEARCH_API_KEY = "";
    var GOOGLE_CUSTOM_SEARCH_ENGINE_ID = "";
    var ALGOLIA_API_KEY = "";
    var ALGOLIA_APP_ID = "";
    var ALGOLIA_INDEX_NAME = "";
    var AZURE_SERVICE_NAME = "";
    var AZURE_INDEX_NAME = "";
    var AZURE_QUERY_KEY = "";
    var BAIDU_API_ID = "";
    var SEARCH_SERVICE = "hexo" || "hexo";
    var ROOT = "/hblog/"||"/";
    if(!ROOT.endsWith('/'))ROOT += '/';
  </script>

<script src="//instant.page/1.2.2" type="module" integrity="sha384-2xV8M5griQmzyiY3CDqh1dn4z3llDVqZDqzjzcY+jCBCk/a5fXJmuZ/40JJAPeoU"></script>


  <script async src="https://cdn.jsdelivr.net/npm/scrollreveal@4.0.5/dist/scrollreveal.min.js"></script>
  <script type="text/javascript">
    $(function() {
      const $reveal = $('.reveal');
      if ($reveal.length === 0) return;
      const sr = ScrollReveal({ distance: 0 });
      sr.reveal('.reveal');
    });
  </script>


  <script src="https://cdn.jsdelivr.net/npm/node-waves@0.7.6/dist/waves.min.js"></script>
  <script type="text/javascript">
    $(function() {
      Waves.attach('.flat-btn', ['waves-button']);
      Waves.attach('.float-btn', ['waves-button', 'waves-float']);
      Waves.attach('.float-btn-light', ['waves-button', 'waves-float', 'waves-light']);
      Waves.attach('.flat-box', ['waves-block']);
      Waves.attach('.float-box', ['waves-block', 'waves-float']);
      Waves.attach('.waves-image');
      Waves.init();
    });
  </script>


  <script async src="https://cdn.jsdelivr.net/gh/xaoxuu/cdn-busuanzi@2.3/js/busuanzi.pure.mini.js"></script>


  <!-- fastclick -->
  <script src="https://cdn.jsdelivr.net/npm/fastclick@1.0.6/lib/fastclick.min.js"></script>
  <script>
    document.addEventListener('DOMContentLoaded', function() {
      FastClick.attach(document.body)
    }, false)
  </script>



  
  
  
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-backstretch/2.0.4/jquery.backstretch.min.js"></script>
    <script type="text/javascript">
      $(function(){
        if ('') {
          $('').backstretch(
          ["https://img.vim-cn.com/29/91197b04c13f512f734a76d4ac422d89dbe229.jpg"],
          {
            duration: "6000",
            fade: "2500"
          });
        } else {
          $.backstretch(
          ["https://img.vim-cn.com/29/91197b04c13f512f734a76d4ac422d89dbe229.jpg"],
          {
            duration: "6000",
            fade: "2500"
          });
        }
      });
    </script>
  









  <script src="//cdn1.lncld.net/static/js/3.0.4/av-min.js"></script>
  
    
      
<script src="https://cdn.jsdelivr.net/gh/xaoxuu/volantis@1.0.6/js/volantis.min.js"></script>

    
  
  <script>
  var GUEST_INFO = ['nick','mail','link'];
  var guest_info = 'nick,mail,link'.split(',').filter(function(item){
    return GUEST_INFO.indexOf(item) > -1
  });
  var notify = 'true' == true;
  var verify = 'true' == true;
  var valine = new Valine();
  valine.init({
    el: '#valine_container',
    notify: notify,
    verify: verify,
    guest_info: guest_info,
    
    appId: "DOrDaqtmOYARAQcfWKvJLWHi-MdYXbMMI",
    appKey: "8987UCDFR7NQIATt7ja3iojv",
    placeholder: "快来评论吧~",
    pageSize:'10',
    avatar:'mp',
    lang:'zh-cn',
    highlight:'true'
  })
  </script>



  
<script src="https://cdn.jsdelivr.net/gh/xaoxuu/cdn-material-x@19.11/js/app.js"></script>



  
<script src="https://cdn.jsdelivr.net/gh/xaoxuu/cdn-material-x@19.11/js/search.js"></script>





<!-- 复制 -->
<script src="https://cdn.jsdelivr.net/npm/clipboard@2/dist/clipboard.min.js"></script>
<script>
  let COPY_SUCCESS = "Copied";
  let COPY_FAILURE = "Copy failed";
  /*页面载入完成后，创建复制按钮*/
  !function (e, t, a) {
    /* code */
    var initCopyCode = function(){
      var copyHtml = '';
      copyHtml += '<button class="btn-copy" data-clipboard-snippet="">';
      copyHtml += '  <i class="fa fa-copy"></i><span>Copy</span>';
      copyHtml += '</button>';
      $(".highlight .code pre").before(copyHtml);
      var clipboard = new ClipboardJS('.btn-copy', {
        target: function(trigger) {
          return trigger.nextElementSibling;
        }
      });

      clipboard.on('success', function(e) {
        //您可以加入成功提示
        console.info('Action:', e.action);
        console.info('Text:', e.text);
        console.info('Trigger:', e.trigger);
        success_prompt(COPY_SUCCESS);
        e.clearSelection();
      });
      clipboard.on('error', function(e) {
        //您可以加入失败提示
        console.error('Action:', e.action);
        console.error('Trigger:', e.trigger);
        fail_prompt(COPY_FAILURE);
      });
    }
    initCopyCode();

  }(window, document);

  /**
   * 弹出式提示框，默认1.5秒自动消失
   * @param message 提示信息
   * @param style 提示样式，有alert-success、alert-danger、alert-warning、alert-info
   * @param time 消失时间
   */
  var prompt = function (message, style, time)
  {
      style = (style === undefined) ? 'alert-success' : style;
      time = (time === undefined) ? 1500 : time*1000;
      $('<div>')
          .appendTo('body')
          .addClass('alert ' + style)
          .html(message)
          .show()
          .delay(time)
          .fadeOut();
  };

  // 成功提示
  var success_prompt = function(message, time)
  {
      prompt(message, 'alert-success', time);
  };

  // 失败提示
  var fail_prompt = function(message, time)
  {
      prompt(message, 'alert-danger', time);
  };

  // 提醒
  var warning_prompt = function(message, time)
  {
      prompt(message, 'alert-warning', time);
  };

  // 信息提示
  var info_prompt = function(message, time)
  {
      prompt(message, 'alert-info', time);
  };

</script>


<!-- fancybox -->
<script src="https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.js"></script>
<script>
  let LAZY_LOAD_IMAGE = "";
  $(".article-entry").find("fancybox").find("img").each(function () {
      var element = document.createElement("a");
      $(element).attr("data-fancybox", "gallery");
      $(element).attr("href", $(this).attr("src"));
      /* 图片采用懒加载处理时,
       * 一般图片标签内会有个属性名来存放图片的真实地址，比如 data-original,
       * 那么此处将原本的属性名src替换为对应属性名data-original,
       * 修改如下
       */
       if (LAZY_LOAD_IMAGE) {
         $(element).attr("href", $(this).attr("data-original"));
       }
      $(this).wrap(element);
  });
</script>





  <script>setLoadingBarProgress(100);</script>
</body>
</html>
